package com.xingchi.tornado.log.aspect;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.xingchi.tornado.core.utils.RequestUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.IntStream;

/**
 * web日志记录切面
 *
 * @author xiaoya
 * @date 2024/9/26 11:17
 */
@Aspect
public class WebLogAspect {

    /**
     * 慢接口判定时间
     */
    private final Integer slowLogInterval;

    public WebLogAspect(Integer slowLogInterval) {
        this.slowLogInterval = slowLogInterval;
        log.info("enable weblog component!");
    }

    private static final Logger log = LoggerFactory.getLogger(WebLogAspect.class);
    private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();

    @Pointcut("@within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Controller)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = Optional.ofNullable(PARAMETER_NAME_DISCOVERER.getParameterNames(method)).orElse(new String[0]);
        Parameter[] parameters = method.getParameters();

        // 分类存储请求参数
        Map<String, Object> pathParams = new HashMap<>();
        Map<String, Object> queryParams = new HashMap<>();
        Map<String, Object> bodyParams = new HashMap<>();

        try {
            for (int i = 0; i < parameterNames.length; i++) {
                String parameterName = parameterNames[i];
                Object parameterValue = args[i];
                if (parameterValue != null && !ignoreArgs(parameterValue)) {
                    // 获取参数上的注解
                    Annotation[] annotations = parameters[i].getAnnotations();
                    boolean categorized = false;
                    for (Annotation annotation : annotations) {
                        if (annotation instanceof RequestParam) {
                            RequestParam requestParam = (RequestParam) annotation;
                            String name = requestParam.value().isEmpty() ? parameterName : requestParam.value();
                            queryParams.put(name, parameterValue);
                            categorized = true;
                        } else if (annotation instanceof PathVariable) {
                            PathVariable pathVariable = (PathVariable) annotation;
                            String name = pathVariable.value().isEmpty() ? parameterName : pathVariable.value();
                            pathParams.put(name, parameterValue);
                            categorized = true;
                        } else if (annotation instanceof RequestBody) {
                            bodyParams.put(parameterName, parameterValue);
                            categorized = true;
                        }
                    }

                    // 如果没有归类，则默认作为query参数
                    if (!categorized) {
                        queryParams.put(parameterName, parameterValue);
                    }
                }
            }
        } catch (Exception e) {
            log.warn("请求参数记录失败，原因：{}", e.getMessage(), e);
        }

        long start = System.currentTimeMillis();
        Object proceed = joinPoint.proceed();
        long end = System.currentTimeMillis();

        // 响应参数处理
        String responseResult = formatResponseResult(proceed);

        HttpServletRequest request = RequestUtils.getRequest();
        String typeName = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        String uri = request.getRequestURI();
        if ((end - start) > slowLogInterval) {
            log.warn("slow interface request，url:{}, method: {}#{}, execution time: {}ms", uri, typeName, methodName, (end - start));
        }

        if (log.isInfoEnabled()) {
            log.info("request  url       : {}", uri);
            log.info("request  method    : {}#{}", typeName, methodName);
            ValueFilter filter = (object, name, value) -> value != null && ignoreArgs(value) ? String.format("字段[%s]类型[%s]类无法转JSON", name, value.getClass().getSimpleName()) : value;
            log.info("request  query     : {}", JSON.toJSONString(queryParams, filter, JSONWriter.Feature.WriteMapNullValue));
            log.info("request  path      : {}", JSON.toJSONString(pathParams));
            log.info("request  body      : {}", JSON.toJSONString(bodyParams));
            log.info("response parameter : {}", responseResult);
            log.info("execution time     : {}ms", (end - start));
        }
        return proceed;
    }

    /**
     * 格式化响应结果
     *
     * @param result 响应结果
     * @return 格式化后的响应结果字符串
     */
    private String formatResponseResult(Object result) {
        if (result == null) {
            return "null";
        }

        try {
            // 如果是基本数据类型或String，直接返回其值
            if (isPrimitiveOrWrapper(result.getClass()) || result instanceof String) {
                return String.valueOf(result);
            }

            // 其他类型转为JSON
            return JSONObject.toJSONString(result);
        } catch (Exception e) {
            log.warn("响应参数记录失败，原因：{}", e.getMessage(), e);
            return "转换失败: " + e.getMessage();
        }
    }

    /**
     * 判断是否为基本数据类型或其包装类
     *
     * @param clazz 类
     * @return 是否为基本数据类型或其包装类
     */
    private boolean isPrimitiveOrWrapper(Class<?> clazz) {
        return clazz.isPrimitive() ||
                clazz == Boolean.class ||
                clazz == Character.class ||
                clazz == Byte.class ||
                clazz == Short.class ||
                clazz == Integer.class ||
                clazz == Long.class ||
                clazz == Float.class ||
                clazz == Double.class;
    }

    /**
     * 判断是否忽略参数
     *
     * @param parameterValue 参数值
     * @return 是否忽略
     */
    public static boolean ignoreArgs(Object parameterValue) {
        Class<?> clazz = parameterValue.getClass();

        // 处理数组
        if (clazz.isArray()) {
            return IntStream.range(0, Array.getLength(parameterValue))
                    .anyMatch(index -> ignoreArgs(Array.get(parameterValue, index)));
        }

        // 处理集合
        if (Collection.class.isAssignableFrom(clazz)) {
            return ((Collection<?>) parameterValue).stream()
                    .anyMatch((Predicate<Object>) WebLogAspect::ignoreArgs);
        }

        return parameterValue instanceof MultipartFile
                || parameterValue instanceof HttpServletRequest
                || parameterValue instanceof HttpServletResponse
                || parameterValue instanceof BindingResult;
    }
}
